import java.util.ArrayList;

/**
 * Created by IntelliJ IDEA.
 * User: Rajeev
 * Date: Dec 27, 2009
 * Time: 2:55:07 PM
 * To change this template use File | Settings | File Templates.
 */
public class RecursiveDescent {

    Element root = null;
    Node dsdtree = null;

    public RecursiveDescent(Element root) {
        this.root = root;
    }

    public void start() throws Exception {
        dsdtree = schema(this.root);

        PrettyPrinter pp = new PrettyPrinter();
        pp.print(dsdtree, "printout.txt");

    }

    boolean invalid(Element e) {
        // todo - complete correctly.
        if (!e.getName().contains(":")) {
//            return true;
            return false;

        }
        return false;
    }

    boolean e(String first, String second) {
        if (first == null || second == null) {
            return false;
        }
        if (first.equals(second)) {
            return true;
        }
        return false;
    }


    /*
 SCHEMA	 ::=  	<dsd  ( root="PENAME" )? >
( RULE  |  DEFINITION  |  SCHEMA )*
</dsd>
    */
    private Node schema(Element e) throws SyntaxException {
        Node n = new Node();

        if (e("dsd", e.getName())) {
            n.setType("dsd");
            n.meta = "SCHEMA";
            Node c = null;
            for (Element child : e.getChildren()) {
                if ((c = rule(child)) != null || (c = definition(child)) != null || (c = schema(child)) != null) {
                    n.addChild(c);
                } else {
                    // todo enable if needed.
                    validate(child);
//                      throw new SyntaxException("INvalid tag inside schema.");
                }

            }
            return n;

        } else {
            return null;
        }
    }

    void validate(Element e) throws SyntaxException {
        if (invalid(e)) {
            throw new SyntaxException("invalid tag:" + e.getName());
        }
    }


    /*
     RULE	 ::=  	<declare> DECLARATION* </declare>
     | 	<require> BOOLEXP* </require>
     | 	<if> BOOLEXP RULE* </if>
     | 	<rule ref="PENAME"/>
     | 	UNIQUE
     | 	POINTER
     */
    Node rule(Element e) throws SyntaxException {
        Node n = new Node();
        n.meta = "RULE";
        String tag = e.getName();
        Node child = null;
        if (e("declare", tag)) {
            for (Element xc : e.getChildren()) {
                if ((child = declaration(xc)) != null) {
                    n.addChild(child);
                } else {
                    if (invalid(e)) {
                        throw new SyntaxException("Invalid tag inside RULE.");

                    }
                }
            }

            return n;
        } else if (e("require", tag)) {
            for (Element xc : e.getChildren()) {
                if ((child = boolexp(xc)) != null) {
                    n.addChild(child);
                } else {
                    if (invalid(e)) {
                        throw new SyntaxException("Invalid tag inside RULE.");
                    }
                }
            }

            return n;
        } else if (e("if", tag)) {
            ArrayList<Element> list = this.cloneChildren(e);
            child = boolexp(list.get(0));
            if (child == null) {
                throw new SyntaxException("Invalid tag inside rule.");

            }
            n.addChild(child);
            list.remove(0);
            for (Element xc : list) {
                child = rule(xc);
                if (child != null) {
                    n.addChild(child);
                } else {
                    validate(e);
                }
            }
            return n;
        } else if (e("rule", tag)) {
            String at = e.getAttribute("ref");
            if (at == null) {
                out("ref attribute null");
            } else {
                Attribute a = new Attribute();
                penameValidate();
                a.type = "ref";    // todo validate pename
                a.value = at;
                n.addAttribute(a);

            }
            n.inline = true;
            return n;
        } else {
            // unique pointer
            if ((child = unique(e)) != null || (child = pointer(e)) != null) {
                return child;
            } else {
                validate(e);
            }
        }


        return null;
    }

    void penameValidate() throws SyntaxException {
        // todo validate pename.
    }


    ArrayList<Element> cloneChildren(Element e) {
        return (ArrayList<Element>) e.getChildren().clone();
    }

    void out(String param) throws SyntaxException {
        throw new SyntaxException(param);
    }


    /*
    DECLARATION	 ::=  	ATTRIBUTEDECL
	| 	<required> ATTRIBUTEDECL* </required>
	| 	<contents> ( REGEXP  |  NORMALIZE  |  CONTENTSDEFAULT )* </contents>
     */
    Node declaration(Element e) throws SyntaxException {
        Node n = new Node();
        n.meta = "DECLARATION";
        Node child = null;
        String tag = e.getName();
        if ((child = attributedecl(e)) != null) {
            n.addChild(child);
            return n;

        } else if (e("required", tag)) {
            for (Element ch : e.getChildren()) {
                child = attributedecl(ch);
                if (child != null) {
                    n.addChild(child);
                } else {
                    validate(e);
                }
            }
            return n;

        } else if (e("contents", c.contents)) {
            for (Element ch : e.getChildren()) {
                if ((child = regexp(ch)) != null || (child = normalize(ch)) != null || (child = contentsDefault(ch)) != null) {
                    n.addChild(child);
                } else {
                    validate(ch);
                }
            }
            return n;

        } else {
            validate(e);
        }
        return null;
    }


    /*
   ATTRIBUTEDECL	 ::=  	<attribute  ( name="PANAME" )? ( type="ATTRTYPE" )? >
( REGEXP  |  NORMALIZE  |  ATTRIBUTEDEFAULT )*
</attribute>
    */
    Node attributedecl(Element e) throws SyntaxException {

        if (e(c.attribute, e.getName())) {
            Node n = new Node();
            n.meta = m.ATTRIBUTEDECL;
            n.type = c.attribute;
            Attribute name = this.paname("name", e); // todo paname validate.
            Attribute type = this.attrtype("type", e); // todo paname validate.
            if (name != null) {
                n.addAttribute(name);
            }
            if (type != null) {
                n.addAttribute(type);
            }
            Node child = null;

            for (Element ch : e.getChildren()) {
                if ((child = regexp(ch)) != null || (child = normalize(ch)) != null || (child = attributeDefault(ch)) != null) {
                    n.addChild(child);
                } else {
                    validate(ch);
                }

            }

            return n;
        }
        return null;
    }

    Attribute paname(String name, Element e) throws SyntaxException {
        // paname
        String at = e.getAttribute(name);
        if (at != null) {
            Attribute a = new Attribute();
            a.type = name;
            a.value = at;
            // todo validate.
            return a;
        }
        return null;
    }

    /*
    ATTRTYPE	 ::=  	string  |  qname  |  qaname
     */
    Attribute attrtype(String name, Element e) throws SyntaxException {
        String at = e.getAttribute(name);
        if (at != null) {
            if (e(c.string, at) || e(c.qname, at) || e(c.qaname, at)) {
                Attribute a = new Attribute();
                a.type = name;
                a.value = at;

                return a;
            } else {
                out("invalid attrtype.");
            }
        }
        return null;
    }

    /*
    BOOLEXP	 ::=  	<and> BOOLEXP* </and>
	| 	<or> BOOLEXP* </or>
	| 	<not> BOOLEXP </not>
	| 	<imply> BOOLEXP BOOLEXP </imply>
	| 	<equiv> BOOLEXP* </equiv>
	| 	<one> BOOLEXP* </one>
	| 	<parent> BOOLEXP </parent>
	| 	<ancestor> BOOLEXP </ancestor>
	| 	<child> BOOLEXP </child>
	| 	<descendant> BOOLEXP </descendant>
	| 	<this/>
	| 	<element ( name="PENAME" )? />
	| 	<attribute ( name="PANAME" )? > REGEXP? </attribute>
	| 	<contents> REGEXP* </contents>
	| 	<boolexp ref="PENAME"/>
     */
    Node boolexp(Element e) throws SyntaxException {
        


        return new Node();
    }

    /*
    REGEXP	 ::=  	<sequence> REGEXP* </sequence>
	| 	<optional> REGEXP </optional>
	| 	<complement> REGEXP </complement>
	| 	<union> REGEXP* </union>
	| 	<intersection> REGEXP* </intersection>
	| 	<minus> REGEXP REGEXP </minus>
	| 	<repeat ( ( number="NUMERAL" )?  |  ( min="NUMERAL" )? ( max="NUMERAL" )? ) > REGEXP </repeat>
	| 	<string ( value="VALUE" )? />
	| 	<char ( ( set="VALUE" )?  |  min="CHAR" max="CHAR" ) />
	| 	<stringtype ref="PENAME"/>
	| 	<contenttype ref="PENAME"/>
	| 	BOOLEXP
     */
    Node regexp(Element e) throws SyntaxException {
        return null;
    }


    /*
    DEFINITION	 ::=  	<rule id="PENAME"> RULE* </rule>
   | 	<contenttype id="PENAME"> REGEXP </contenttype>
   | 	<stringtype id="PENAME"> REGEXP </stringtype>
   | 	<boolexp id="PENAME"> BOOLEXP </boolexp>
    */
    Node definition(Element e) throws SyntaxException {

        return null;
    }

    /*
    NORMALIZE	 ::=  	<normalize  ( whitespace="WHITESPACE" )?  ( case="CASE" )? />
     */
    Node normalize(Element e) throws SyntaxException {
        return null;
    }

    /*
    WHITESPACE	 ::=  	preserve  |  compress  |  trim
     */
    Attribute whitespace(Element e) throws SyntaxException {
        Attribute a = new Attribute();
        String at = e.getAttribute(c.whitespace);
        if (at != null) {
            if (e(at, c.preserve) || e(at, c.compress) || e(at, c.trim)) {
                a.type = c.whitespace;
                a.value = at;
                return a;
            } else {
                out("invalid whitespace attribute");
            }
        }
        return null;
    }

    /*
    CASE	 ::=  	preserve  |  upper  |  lower
     */
    Attribute Case(Element e) throws SyntaxException {
        Attribute a = new Attribute();
        String at = e.getAttribute(c.Case);
        if (at != null) {
            if (e(at, c.preserve) || e(at, c.upper) || e(at, c.lower)) {
                a.type = c.Case;
                a.value = at;
                return a;
            } else {
                out("invalid case attribute");
            }
        }
        return null;
    }

    /*
    ATTRIBUTEDEFAULT	 ::=  	<default value="VALUE" />
     */
    Node attributeDefault(Element e) throws SyntaxException {
        if (e(e.getName(), c.Default)) {
            Node n = new Node();
            n.meta = m.ATTRIBUTEDEFAULT;
            n.inline = true;
            Attribute a = getAttribute("value", e);

            if (a != null) {
                n.addAttribute(a);
                return n;
            }
        }

        return null;
    }

    Attribute getAttribute(String name, Element e) {
        Attribute a = new Attribute();
        String at = e.getAttribute(name);
        if (at != null) {
            a.type = name;
            a.value = at;
            return a;
        }
        return null;
    }

    /*
    CONTENTSDEFAULT	 ::=  	<default> ANYCONTENTS </default>
    */
    Node contentsDefault(Element e) throws SyntaxException {
        // todo validate any content
        if (e(c.Default, e.getName())) {
            Node n = new Node();
            n.type = c.Default;
            n.meta = m.CONTENTSDEFAULT;
            n.content = e.getContent();
            return n;

        }
        return null;
    }

    /*
UNIQUE	 ::= 	<unique ( key="VALUE" )? >
  ( BOOLEXP FIELD* | ( <select> BOOLEXP FIELD* </select> )* )
</unique>

     */
    Node unique(Element e) throws SyntaxException {
        return null;
    }


    /*
    POINTER	 ::= 	<pointer ( key="VALUE" )? > BOOLEXP? FIELD* </pointer>
     */
    Node pointer(Element e) throws SyntaxException {

        if (e(c.pointer, e.getName())) {
            Node n = new Node();
            n.type = e.getName();
            n.meta = m.POINTER;

            Attribute a = getAttribute(c.key, e);
            if (a != null) {
                n.addAttribute(a);
            }

            Node child = boolexp(e.getChildren().get(0));
            ArrayList<Element> list = this.cloneChildren(e);
            if (child != null) {
                n.addChild(child);
                list.remove(0);
            }

            for (Element ch : list) {
                if ((child = field(ch)) != null) {
                    n.addChild(child);
                } else {
                    validate(ch);
                }
            }
            return n;
        }

        return null;
    }


    /*
    FIELD	 ::=  	<attributefield name="PANAME" ( type="ATTRTYPE" )? > BOOLEXP? </attributefield>
	| 	<chardatafield ( type="ATTRTYPE" )? > BOOLEXP? </chardatafield>
     */
    Node field(Element e) throws SyntaxException {
        return null;
    }


    // returns true if x matches one of the targets.
    private boolean equalsOR(String x, String... targets) {
        for (String item : targets) {
            if (x.equalsIgnoreCase(item)) {
                return true;
            }
        }
        return false;
    }

    /////////////////////////////////////////////////////////////
    // util methods

    // compares two consecutive items. number of params must be even.

    private boolean equalsConsecutive(String... items) {
        if (items.length % 2 == 1) {
            return false;
        }
        for (int i = 0; i < items.length; i += 2) {
            if (!items[i].equalsIgnoreCase(items[i + 1])) {
                return false;
            }
        }
        return true;
    }

}
